package org.xingole.jgit.lib;

/**
 * <p>Pairing of a name and the {@code ObjectId} it currently has.</p>
 *
 * <p>A ref in Git is (more or less) a variable that holds a single object identifier.
 *    The object identifier can be any valid Git object(blob, tree, commit, annotated tag, ...).</p>
 *
 * <p>The ref name has the attributes of the ref that was asked for as well as the ref it was resolved
 *    to for symbolic refs plus the object id it points to and (for tags) the peeled target object id,
 *    i.e. the tag resolved recursively until a non-tag object is referenced.</p>
 *
 * <p>A <em>symbolic ref</em> is a regular file that stores a string that begins with <code>ref: refs/</code>.
 *    For example, your <code>.git/HEAD</code> is a regular file whose content is <code>ref: refs/heads/master</code>.</p>
 *
 * <p>In a word, A symbolic reference, also known as a symlink
 * , in Git is a special type of reference that points to another reference. </p>
 */
public interface Ref {
    /**********************The common method for Git ref**************************/
    /**
     * What this ref is called within the repository.
     * @return : name of this ref.
     */
      String getName();

    /**
     * <p>Test if this reference is a symbolic reference.</p>
     *
     * <p>A symbolic reference does not have its own {@code ObjectId} value, but instead points to another Ref in the
     *    same database and always uses that other reference's value as its own.</p>
     * @return : true if this is a symbolic reference; false if this reference contains its own ObjectId.
     */
    boolean isSymbolic();

    // Methods for ordinary reference

    /**
     * Cached value of this ref.
     * @return : the value of this ref at the last time we read it. May be null to indicate a ref that does not exist
     *          yet or a symbolic ref pointing to an unborn branch.
     */
    ObjectId getObjectId();

    // Methods for symbolic reference

    /**
     * <p>Traverse target references until {@code isSymbolic()} is false.</p>
     *
     * <p>If {@code isSymbolic()} is false, return this.</p>
     *
     * <p>If {@code isSymbolic()} is true, this method recursively traverses {@code getTarget()} until
     * {@code isSymbolic()} returns false.</p>
     *
     * <p>This method is effectively</p>
     * <pre>
     *     return isSymbolic() ? getTarget().getLeaf() : this;
     * </pre>
     * @return : the reference that actually stores the ObjectId value.
     */
    Ref getLeaf();

    /**
     * <p>Get the reference this reference points to, or this.</p>
     *
     * <p>If isSymbolic() is true this method returns the reference it directly points to,
     *     which might not be the leaf reference, but could be another symbolic reference.</p>
     *
     * <p>If this is a leaf level reference that contains its own ObjectId,this method returns this.</p>
     *
     * @return : the target reference, or this.
     */
    Ref getTarget();

    /*
     * Peel:
     *      The action of recursively dereferencing a tag object.
     * Dereference:
     *       "Dereferencing" as it used in the context of Git commands or protocols is implicitly recursive.
     *
     * The ref^{} syntax in git is used to refer to the most recent commit that was made to a branch or tag.
     * It is a useful way to get back to a previous state of your repository, without having to manually navigate
     *  through the commit history.
     */

    // Methods for annotated tag reference (not lightweight tag)
    /**
     * <p>Cached value of ref^{} (the ref peeled to commit).</p>
     *
     * @return : if this ref is an annotated tag, return the id of the commit (or tree or blob) that the annotated tag refers
     * to; null if this ref does not refer to an annotated tag.
     */
    ObjectId getPeeledObjectId();

    /**
     * TODO: The peeled tag means what?
     * @return : whether the Ref represents a peeled tag
     */
    boolean isPeeled();

    // Methods for storage way for this reference.

    /**
     * <p>How was this ref obtained?</p>
     *
     * <p>The current storage model of a Ref may influence how the ref must be updated or deleted from the repository.</p>
     *
     * @return : type of ref.
     */
    Storage getStorage();

    /**
     * Location where a Ref is stored. In a word, Storage class models the storage format of Git data.
     */
    public static enum Storage {   // use to enum type to show the states

        /*
         * Implementation notes:
         *
         * When a repo starts up, it usually contains loose objects.
         * When the number of loose objects increases, they become inefficient and are stored in a pack file.
         *
         */

        /**
         * <p>The ref does not exist yet, updating it may create it.</p>
         * <p>Creation is likely to choose LOOSE storage.</p>
         */
        NEW(true, false),

        /**
         * <p>The ref is stored in a file by itself</p>
         * <p>Updating this ref affects only this ref.</p>
         */
        LOOSE(true, false),

        /**
         * <p>The ref is stored in the packed-refs file, with others.</p>
         * <p>Updating this ref requires rewriting the file, with perhaps many other refs being included at the
         * same time.</p>
         */
        PACKED(false, true),

        /**
         * <p>The ref is both LOOSE and PACKED.</p>
         * <p>Updating this ref requires only updating the loose file, but deletion requires both the loose file
         * and the packed refs file.</p>
         */
        LOOSE_PACKED(true, true),

        /**
         * <p>The ref came from a network advertisement and storage is unknown.</p>
         *
         * TODO: How to understand the paragraph meaning?
         *
         * <p>This ref cannot be updated without Git-aware support on the remote side,
         * as Git-aware code consolidate the remote refs and reported them to this process.</p>
         */
        NETWORK(false, false);


        // There are only two type of storage format for git data: loose format and packed format
        private final boolean loose;

        private final boolean packed;

        private Storage(final boolean loose, final boolean packed) {
            this.loose = loose;
            this.packed = packed;
        }

        // The methods to decide which type format the object storage is.
        public boolean isLoose() {
            return loose;
        }

        public boolean isPacked() {
            return packed;
        }
    }
}
